summaryrefslogtreecommitdiff
path: root/src/pages/og-image/[...slug].png.ts
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
committerDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
commit456cf011b36de91c9936994b1fa45703adcd309b (patch)
tree8e60daf998f731ac50d100fa490eaecae1168042 /src/pages/og-image/[...slug].png.ts
Initial fork of chrismwilliams/astro-theme-cactus theme
Diffstat (limited to 'src/pages/og-image/[...slug].png.ts')
-rw-r--r--src/pages/og-image/[...slug].png.ts90
1 files changed, 90 insertions, 0 deletions
diff --git a/src/pages/og-image/[...slug].png.ts b/src/pages/og-image/[...slug].png.ts
new file mode 100644
index 0000000..a4982d8
--- /dev/null
+++ b/src/pages/og-image/[...slug].png.ts
@@ -0,0 +1,90 @@
+import RobotoMonoBold from "@/assets/roboto-mono-700.ttf";
+import RobotoMono from "@/assets/roboto-mono-regular.ttf";
+import { getAllPosts } from "@/data/post";
+import { siteConfig } from "@/site.config";
+import { getFormattedDate } from "@/utils/date";
+import { Resvg } from "@resvg/resvg-js";
+import type { APIContext, InferGetStaticPropsType } from "astro";
+import satori, { type SatoriOptions } from "satori";
+import { html } from "satori-html";
+
+const ogOptions: SatoriOptions = {
+ // debug: true,
+ fonts: [
+ {
+ data: Buffer.from(RobotoMono),
+ name: "Roboto Mono",
+ style: "normal",
+ weight: 400,
+ },
+ {
+ data: Buffer.from(RobotoMonoBold),
+ name: "Roboto Mono",
+ style: "normal",
+ weight: 700,
+ },
+ ],
+ height: 630,
+ width: 1200,
+};
+
+const markup = (title: string, pubDate: string) =>
+ html`<div tw="flex flex-col w-full h-full bg-[#1d1f21] text-[#c9cacc]">
+ <div tw="flex flex-col flex-1 w-full p-10 justify-center">
+ <p tw="text-2xl mb-6">${pubDate}</p>
+ <h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1>
+ </div>
+ <div tw="flex items-center justify-between w-full p-10 border-t border-[#2bbc89] text-xl">
+ <div tw="flex items-center">
+ <svg height="60" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 272 480">
+ <path
+ d="M181.334 93.333v-40L226.667 80v40l-45.333-26.667ZM136.001 53.333 90.667 26.667v426.666L136.001 480V53.333Z"
+ fill="#B04304"
+ ></path>
+ <path
+ d="m136.001 119.944 45.333-26.667 45.333 26.667-45.333 26.667-45.333-26.667ZM90.667 26.667 136.001 0l45.333 26.667-45.333 26.666-45.334-26.666ZM181.334 53.277l45.333-26.666L272 53.277l-45.333 26.667-45.333-26.667ZM0 213.277l45.333-26.667 45.334 26.667-45.334 26.667L0 213.277ZM136 239.944l-45.333-26.667v53.333L136 239.944Z"
+ fill="#FF5D01"
+ ></path>
+ <path
+ d="m136 53.333 45.333-26.666v120L226.667 120V80L272 53.333V160l-90.667 53.333v240L136 480V306.667L45.334 360V240l45.333-26.667v53.334L136 240V53.333Z"
+ fill="#53C68C"
+ ></path>
+ <path d="M45.334 240 0 213.334v120L45.334 360V240Z" fill="#B04304"></path>
+ </svg>
+ <p tw="ml-3 font-semibold">${siteConfig.title}</p>
+ </div>
+ <p>by ${siteConfig.author}</p>
+ </div>
+ </div>`;
+
+type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+export async function GET(context: APIContext) {
+ const { pubDate, title } = context.props as Props;
+
+ const postDate = getFormattedDate(pubDate, {
+ month: "long",
+ weekday: "long",
+ });
+ const svg = await satori(markup(title, postDate), ogOptions);
+ const png = new Resvg(svg).render().asPng();
+ return new Response(png, {
+ headers: {
+ "Cache-Control": "public, max-age=31536000, immutable",
+ "Content-Type": "image/png",
+ },
+ });
+}
+
+export async function getStaticPaths() {
+ const posts = await getAllPosts();
+ return posts
+ .filter(({ data }) => !data.ogImage)
+ .map((post) => ({
+ params: { slug: post.id },
+ props: {
+ pubDate: post.data.updatedDate ?? post.data.publishDate,
+ title: post.data.title,
+ },
+ }));
+}